A previsão de vendas é um processo no qual uma empresa pode estimar suas vendas futuras com base em dados anteriores e comparações do setor. Como um processo, a previsão de vendas permite que as empresas utilizem esses dados para produzir planos de desempenho precisos que podem moldar a maneira como fazem negócios.
Algumas das principais áreas em que uma previsão de vendas pode beneficiar sua empresa são a previsão das receitas de vendas por períodos variados de tempo, o que permite que a empresa atribua recursos apropriadamente onde necessário e desenvolva planos baseados em dados para crescimento e expansão futuros.
O objetivo deste deste projeto é prever as vendas para o ano de 2015 com base nos dados fornecidos. Além de demonstrar algumas conclusôes fundamentada nos dados.
fonte: act, s.d.
# importação de módulos
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
import plotly.express as px
import numpy as np
from pmdarima.metrics import smape
from scipy.stats import boxcox
from scipy.special import inv_boxcox
from sklearn.metrics import mean_squared_error
from math import sqrt
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from pmdarima import arima
import pmdarima as pm
from pmdarima import model_selection
import plotly.graph_objects as go
from pmdarima.arima import auto_arima
from pmdarima.model_selection import train_test_split
from pmdarima.utils import tsdisplay
O conjunto de dados pode ser baixado neste link.
# obtenção dos dados
df = pd. read_csv("superstore_dataset2011-2015.csv", encoding= 'unicode_escape')
# visualização das primeiras 5 linhas do dataframe
df.head()
| Row ID | Order ID | Order Date | Ship Date | Ship Mode | Customer ID | Customer Name | Segment | City | State | ... | Product ID | Category | Sub-Category | Product Name | Sales | Quantity | Discount | Profit | Shipping Cost | Order Priority | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 42433 | AG-2011-2040 | 1/1/2011 | 6/1/2011 | Standard Class | TB-11280 | Toby Braunhardt | Consumer | Constantine | Constantine | ... | OFF-TEN-10000025 | Office Supplies | Storage | Tenex Lockers, Blue | 408.300 | 2 | 0.0 | 106.140 | 35.46 | Medium |
| 1 | 22253 | IN-2011-47883 | 1/1/2011 | 8/1/2011 | Standard Class | JH-15985 | Joseph Holt | Consumer | Wagga Wagga | New South Wales | ... | OFF-SU-10000618 | Office Supplies | Supplies | Acme Trimmer, High Speed | 120.366 | 3 | 0.1 | 36.036 | 9.72 | Medium |
| 2 | 48883 | HU-2011-1220 | 1/1/2011 | 5/1/2011 | Second Class | AT-735 | Annie Thurman | Consumer | Budapest | Budapest | ... | OFF-TEN-10001585 | Office Supplies | Storage | Tenex Box, Single Width | 66.120 | 4 | 0.0 | 29.640 | 8.17 | High |
| 3 | 11731 | IT-2011-3647632 | 1/1/2011 | 5/1/2011 | Second Class | EM-14140 | Eugene Moren | Home Office | Stockholm | Stockholm | ... | OFF-PA-10001492 | Office Supplies | Paper | Enermax Note Cards, Premium | 44.865 | 3 | 0.5 | -26.055 | 4.82 | High |
| 4 | 22255 | IN-2011-47883 | 1/1/2011 | 8/1/2011 | Standard Class | JH-15985 | Joseph Holt | Consumer | Wagga Wagga | New South Wales | ... | FUR-FU-10003447 | Furniture | Furnishings | Eldon Light Bulb, Duo Pack | 113.670 | 5 | 0.1 | 37.770 | 4.70 | Medium |
5 rows × 24 columns
# informações sobre o índice dtype e colunas, valores não nulos e uso de memória.
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 51290 entries, 0 to 51289 Data columns (total 24 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Row ID 51290 non-null int64 1 Order ID 51290 non-null object 2 Order Date 51290 non-null object 3 Ship Date 51290 non-null object 4 Ship Mode 51290 non-null object 5 Customer ID 51290 non-null object 6 Customer Name 51290 non-null object 7 Segment 51290 non-null object 8 City 51290 non-null object 9 State 51290 non-null object 10 Country 51290 non-null object 11 Postal Code 9994 non-null float64 12 Market 51290 non-null object 13 Region 51290 non-null object 14 Product ID 51290 non-null object 15 Category 51290 non-null object 16 Sub-Category 51290 non-null object 17 Product Name 51290 non-null object 18 Sales 51290 non-null float64 19 Quantity 51290 non-null int64 20 Discount 51290 non-null float64 21 Profit 51290 non-null float64 22 Shipping Cost 51290 non-null float64 23 Order Priority 51290 non-null object dtypes: float64(5), int64(2), object(17) memory usage: 9.4+ MB
# estatística descritiva das colunas númericas
df.describe()
| Row ID | Postal Code | Sales | Quantity | Discount | Profit | Shipping Cost | |
|---|---|---|---|---|---|---|---|
| count | 51290.00000 | 9994.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 |
| mean | 25645.50000 | 55190.379428 | 246.490581 | 3.476545 | 0.142908 | 28.610982 | 26.375915 |
| std | 14806.29199 | 32063.693350 | 487.565361 | 2.278766 | 0.212280 | 174.340972 | 57.296804 |
| min | 1.00000 | 1040.000000 | 0.444000 | 1.000000 | 0.000000 | -6599.978000 | 0.000000 |
| 25% | 12823.25000 | 23223.000000 | 30.758625 | 2.000000 | 0.000000 | 0.000000 | 2.610000 |
| 50% | 25645.50000 | 56430.500000 | 85.053000 | 3.000000 | 0.000000 | 9.240000 | 7.790000 |
| 75% | 38467.75000 | 90008.000000 | 251.053200 | 5.000000 | 0.200000 | 36.810000 | 24.450000 |
| max | 51290.00000 | 99301.000000 | 22638.480000 | 14.000000 | 0.850000 | 8399.976000 | 933.570000 |
# verificando se há registros duplicados
a = df.duplicated().sum()
print(f'Há um total de {a} dados duplicados.')
Há um total de 0 dados duplicados.
# verificando se há registros nulos
df.isna().sum()
Row ID 0 Order ID 0 Order Date 0 Ship Date 0 Ship Mode 0 Customer ID 0 Customer Name 0 Segment 0 City 0 State 0 Country 0 Postal Code 41296 Market 0 Region 0 Product ID 0 Category 0 Sub-Category 0 Product Name 0 Sales 0 Quantity 0 Discount 0 Profit 0 Shipping Cost 0 Order Priority 0 dtype: int64
# remover colunas com muitos registros nulos ou que não serão utilizados
df = df.drop(columns=['Postal Code', 'Row ID', 'Order ID', 'Customer ID', 'Product ID'])
# visualizar o dataframe atualizado
df.head(1)
| Order Date | Ship Date | Ship Mode | Customer Name | Segment | City | State | Country | Market | Region | Category | Sub-Category | Product Name | Sales | Quantity | Discount | Profit | Shipping Cost | Order Priority | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1/1/2011 | 6/1/2011 | Standard Class | Toby Braunhardt | Consumer | Constantine | Constantine | Algeria | Africa | Africa | Office Supplies | Storage | Tenex Lockers, Blue | 408.3 | 2 | 0.0 | 106.14 | 35.46 | Medium |
segmento = df.groupby(['Segment']).sum().sort_values(by='Sales', ascending=False)
segmento = segmento.reset_index()
f, ax = plt.subplots(figsize=(15, 8))
g = sns.barplot(x="Segment", y=segmento['Sales']*(10e-7), data=segmento, palette = sns.color_palette("Set2"))
g.set(ylabel="Valor de vendas (em milhões)")
g.set(xlabel="Segmento dos clientes")
g.set_title("Vendas por segmento dos clientes", weight='bold')
Text(0.5, 1.0, 'Vendas por segmento dos clientes')
# dataframe ordenado por países com mais vendas
df_country = df.groupby(['Country']).sum().sort_values(by='Sales', ascending=False)
df_country = df_country.reset_index()
df_country.head(10)
| Country | Sales | Quantity | Discount | Profit | Shipping Cost | |
|---|---|---|---|---|---|---|
| 0 | United States | 2.297201e+06 | 37873 | 1561.090 | 286397.02170 | 238173.79 |
| 1 | Australia | 9.252359e+05 | 10673 | 407.200 | 103907.43300 | 100359.02 |
| 2 | France | 8.589311e+05 | 10804 | 204.350 | 109029.00300 | 95387.81 |
| 3 | China | 7.005620e+05 | 7081 | 26.900 | 150683.08500 | 78957.02 |
| 4 | Germany | 6.288400e+05 | 7745 | 117.800 | 107322.82050 | 63965.18 |
| 5 | Mexico | 6.225906e+05 | 10011 | 162.930 | 102818.09752 | 67659.85 |
| 6 | India | 5.896501e+05 | 5758 | 27.500 | 129071.83500 | 61780.72 |
| 7 | United Kingdom | 5.285763e+05 | 6161 | 107.300 | 111900.15000 | 53580.27 |
| 8 | Indonesia | 4.048875e+05 | 5237 | 413.260 | 15608.67790 | 43948.62 |
| 9 | Brazil | 3.611064e+05 | 6148 | 232.822 | 30090.49896 | 38170.73 |
sns.set(font_scale = 1.2)
f, ax = plt.subplots(figsize=(15, 30))
g = sns.barplot(x=df_country['Sales']*(10e-7), y="Country", data=df_country, color="y")
g.set(xlabel="Valor de vendas (em milhões)")
g.set(ylabel="Países")
g.set_title("Ranking de países por vendas ", weight='bold')
Text(0.5, 1.0, 'Ranking de países por vendas ')
sns.set(font_scale = 1.2)
f, ax = plt.subplots(figsize=(10, 15))
g = sns.barplot(x=df_country['Profit']*(10e-7), y="Country", data=df_country[:20], color="y")
g.set(xlabel="Valor de vendas (em milhões)")
g.set(ylabel="Países")
g.set_title("Top 20 países em vendas ", weight='bold')
Text(0.5, 1.0, 'Top 20 países em vendas ')
#top 20 cidades em vendas
city = df.groupby(['City']).count().sort_values(by='Sales', ascending=False)
city = city.reset_index()
city = city[:20]
sns.set(font_scale = 1.2)
f, ax = plt.subplots(figsize=(10, 15))
g = sns.barplot(x="Sales", y="City", data=city, color='b') #palette= sns.dark_palette("#69d", reverse=True))
g.set(xlabel="Valor de vendas (em milhares)")
g.set(ylabel="Cidades")
g.set_title("Top 20 cidades por valor de venda", weight='bold')
Text(0.5, 1.0, 'Top 20 cidades por valor de venda')
fig = px.pie(city, values='Sales', names='City', title='Top 20 cidades em vendas', color_discrete_sequence=px.colors.qualitative.Plotly)
fig.update_traces(textposition='auto', textinfo='percent+label')
fig.show()
fig = px.pie(df, values='Sales', names='Market', title='Vendas por região', color_discrete_sequence=px.colors.qualitative.G10)
fig.update_traces(textposition='auto', textinfo='percent+label')
fig.show()
# dataframe ordenado por países com mais lucro
df_country = df.groupby(['Country']).sum().sort_values(by='Profit', ascending=False)
df_country = df_country.reset_index()
df_country.head(10)
sns.set(font_scale = 1.2)
f, ax = plt.subplots(figsize=(10, 15))
g = sns.barplot(x=df_country['Profit'], y="Country", data=df_country[:30], color="r")
g.set(xlabel="Lucro")
g.set(ylabel="Países")
g.set_title("Top 20 países em lucro ", weight='bold')
Text(0.5, 1.0, 'Top 20 países em lucro ')
# gráfico de lucro por região
lucro = df.groupby(by=['Market']).sum().sort_values(by='Profit', ascending=False)
fig = px.bar(lucro, x=lucro.index, y='Profit')
fig.show()
# porcentagem de lucro por região
fig = px.pie(df, values='Profit', names='Market', title='Lucro por região', color='Market')
fig.update_traces(textposition='auto', textinfo='percent+label')
fig.show()
df_category = df.groupby(['Category']).sum().sort_values(by='Sales', ascending=False)
df_category = df_category.reset_index()
f, ax = plt.subplots(figsize=(15, 8))
sns.set(font_scale = 1.5)
g = sns.barplot(x="Category", y=df_category['Sales']*(10e-7), data=df_category, color='b')
g.set(ylabel="Valor de vendas (em milhões)")
g.set(xlabel="Categorias")
g.set_title("Vendas por categorias", weight='bold')
Text(0.5, 1.0, 'Vendas por categorias')
df_category = df.groupby(['Sub-Category']).sum().sort_values(by='Sales', ascending=False)
df_category = df_category.reset_index()
f, ax = plt.subplots(figsize=(20, 8))
sns.set(font_scale = 1)
g = sns.barplot(x="Sub-Category", y=df_category['Sales']*(10e-7), data=df_category, palette= sns.color_palette("Set2"))
g.set(ylabel="Valor de vendas (em milhões)")
g.set(xlabel="Sub-Categorias")
g.set_title("Vendas por subcategorias", weight='bold')
Text(0.5, 1.0, 'Vendas por subcategorias')
# Quantidade de produtos vendidos
df_category = df.groupby(['Sub-Category']).sum().sort_values(by='Quantity', ascending=False)
df_category = df_category.reset_index()
f, ax = plt.subplots(figsize=(24, 8))
sns.barplot(x="Sub-Category", y="Quantity", data=df_category, palette=sns.color_palette("Paired"))
<AxesSubplot:xlabel='Sub-Category', ylabel='Quantity'>
# top 10 clientes com maiores compras
customers = df.groupby(['Customer Name']).sum().sort_values(by='Sales', ascending=False)
customers = customers.reset_index()
f, ax = plt.subplots(figsize=(15, 8))
sns.barplot(x="Sales", y="Customer Name", data=customers[:10])
<AxesSubplot:xlabel='Sales', ylabel='Customer Name'>
f, ax = plt.subplots(figsize=(12, 8))
g = sns.barplot(x='Ship Mode', y='Shipping Cost', data=df , color='r')
g.set(ylabel="Custo de entrega")
g.set(xlabel="Tipo de entrega")
g.set_title('Custo de entrega por tipo', weight='bold')
plt.show()
f, ax = plt.subplots(figsize=(12, 8))
g = sns.barplot(x='Sales', y='Ship Mode', data=df , color='g')
g.set(xlabel="Valor de venda")
g.set(ylabel="Tipo de entrega")
g.set_title('Custo de entrega por valor de venda', weight='bold')
plt.show()
# custo de entrega por região
f, ax = plt.subplots(figsize=(18, 8))
g = sns.barplot(x='Region', y='Shipping Cost', data=df, palette=sns.color_palette("Paired"))
g.set(ylabel="Custo de entrega")
g.set(xlabel="Região")
g.set_title('Custo de entrega por região', weight='bold')
plt.show()
# média do custo de entrega mensal por valor de venda
shipping = df.sort_values(by='Shipping Cost', ascending=False)
shipping['Order Date'] = pd.to_datetime(shipping['Order Date'])
shipping = shipping.set_index('Order Date')
shipping = shipping.resample('M').mean()
g = sns.lmplot(x='Shipping Cost', y='Sales', data=shipping, height=10)
g.set(ylabel="Valor de venda médio mensal")
g.set(xlabel="Média do custo de entrega por mês")
plt.show()
# lucro vs custo de entrega
shipping = df.sort_values(by='Shipping Cost', ascending=False)
shipping['Order Date'] = pd.to_datetime(shipping['Order Date'])
shipping = shipping.set_index('Order Date')
shipping = shipping.resample('M').mean()
g = sns.lmplot(x='Shipping Cost', y='Profit', data=shipping, height=10)
g.set(ylabel="Lucro médio mensal")
g.set(xlabel="Média do custo de entrega por mês")
plt.show()
# dataframe ordenado pelos maiores descontos
discount = df.sort_values(by='Discount', ascending=False)
f, ax = plt.subplots(figsize=(15, 8))
sns.set(font_scale = 1.5)
g = sns.scatterplot(x="Discount", y="Sales", data=discount, sizes=(20, 20), color='g')
g.set(ylabel="Valor de vendas")
g.set(xlabel="Taxa de desconto")
g.set_title('Desconto por valor de venda', weight='bold')
plt.show()
# gráfico de lucro por desconto desconto
discount = df.sort_values(by='Discount', ascending=False)
discount['Order Date'] = pd.to_datetime(discount['Order Date'])
discount = discount.set_index('Order Date')
discount = discount.resample('M').mean()
g = sns.lmplot(x='Discount', y='Profit', data=df, height=10)
g.set(ylabel="Lucro")
g.set(xlabel="Taxa de desconto")
plt.show()
# dataframe ordenado pelas maiores vendas
maior = df.sort_values(by='Sales', ascending=False)
maior.head(5)
| Order Date | Ship Date | Ship Mode | Customer Name | Segment | City | State | Country | Market | Region | Category | Sub-Category | Product Name | Sales | Quantity | Discount | Profit | Shipping Cost | Order Priority | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 28612 | 2011-03-18 | 23-03-2011 | Standard Class | Sean Miller | Home Office | Jacksonville | Florida | United States | US | South | Technology | Machines | Cisco TelePresence System EX90 Videoconferenci... | 22638.480 | 6 | 0.5 | -1811.0784 | 24.29 | Medium |
| 8898 | 2013-03-10 | 10/10/2013 | Standard Class | Tamara Chand | Corporate | Lafayette | Indiana | United States | US | Central | Technology | Copiers | Canon imageCLASS 2200 Advanced Copier | 17499.950 | 5 | 0.0 | 8399.9760 | 349.07 | Medium |
| 38848 | 2014-03-24 | 26-03-2014 | First Class | Raymond Buch | Consumer | Seattle | Washington | United States | US | West | Technology | Copiers | Canon imageCLASS 2200 Advanced Copier | 13999.960 | 4 | 0.0 | 6719.9808 | 20.00 | Medium |
| 38234 | 2014-10-23 | 25-10-2014 | First Class | Tom Ashbrook | Home Office | New York City | New York | United States | US | East | Technology | Copiers | Canon imageCLASS 2200 Advanced Copier | 11199.968 | 4 | 0.2 | 3919.9888 | 45.98 | High |
| 29946 | 2014-11-18 | 23-11-2014 | Standard Class | Hunter Lopez | Consumer | Newark | Delaware | United States | US | East | Technology | Copiers | Canon imageCLASS 2200 Advanced Copier | 10499.970 | 3 | 0.0 | 5039.9856 | 363.19 | Medium |
# desconto por sub-categorias de produtos
f, ax = plt.subplots(figsize=(20, 8))
sns.set(font_scale = 1.2);
g = sns.barplot(x='Sub-Category', y='Discount', data=df)
g.set(ylabel="Taxa de desconto")
g.set(xlabel="Sub-categoria")
g.set_title('Desconto por sub-categoria de produtos', weight='bold')
plt.show()
# lucro por sub-categorias de produtos
f, ax = plt.subplots(figsize=(20, 8))
sns.set(font_scale = 1.2);
g = sns.barplot(x='Sub-Category', y='Profit', data=df)
g.set(ylabel="Lucro")
g.set(xlabel="Sub-categoria")
g.set_title('Lucro por sub-categoria de produtos', weight='bold')
plt.show()
# definindo a data de compra como índice no formato datetime
df['Order Date'] = pd.to_datetime(df['Order Date'])
df = df.set_index('Order Date')
# redimensionando o dataframe por soma de vendas, quantidade, desconto, lucro e custo de entrega por mês
df = df.resample('M').sum()
# visualizando as primeiros 5 linhas do dataframe modificado
df.head()
| Sales | Quantity | Discount | Profit | Shipping Cost | |
|---|---|---|---|---|---|
| Order Date | |||||
| 2011-01-31 | 138241.30042 | 2178 | 97.596 | 13457.23302 | 14803.89 |
| 2011-02-28 | 134969.94086 | 1794 | 78.516 | 17588.83726 | 15052.66 |
| 2011-03-31 | 171455.59372 | 2183 | 90.814 | 16169.36062 | 17017.34 |
| 2011-04-30 | 128833.47034 | 2181 | 86.630 | 13405.46924 | 14475.01 |
| 2011-05-31 | 148146.72092 | 2057 | 80.052 | 14777.45792 | 15537.48 |
# soma total de vendas por ano
f, ax = plt.subplots(figsize=(15, 8))
vendas_ano = pd.DataFrame(df.groupby(df.index.year).Sales.sum())
g = sns.barplot(x=vendas_ano.index, y=vendas_ano['Sales']*(1e-06), data=vendas_ano, palette = sns.dark_palette("#69d", reverse=True))
g.set(xlabel="Ano")
g.set(ylabel="Vendas (em milhões)")
g.set_title('Total de vendas por ano', weight='bold')
plt.show()
# taxa de crescimento de vendas por ano
taxa = pd.DataFrame(df.groupby(df.index.year).Sales.sum().pct_change().mul(100).round(2))
taxa
| Sales | |
|---|---|
| Order Date | |
| 2011 | NaN |
| 2012 | 18.50 |
| 2013 | 27.20 |
| 2014 | 26.25 |
# gráfico taxa de crescimento de vendas por ano em relação ao ano anterior
f, ax = plt.subplots(figsize=(15, 8))
g = sns.lineplot(x=taxa.index, y='Sales', data=taxa)
g.set(xlabel="Ano")
g.set(ylabel="Taxa de crescimento (%)")
g.set_title('Taxa de crescimento em relação ao ano anterior', weight='bold')
plt.show()
# Separando o dataframe por vendas
df_prev = pd.DataFrame(df.loc[df.index, 'Sales'])
df_prev.head()
| Sales | |
|---|---|
| Order Date | |
| 2011-01-31 | 138241.30042 |
| 2011-02-28 | 134969.94086 |
| 2011-03-31 | 171455.59372 |
| 2011-04-30 | 128833.47034 |
| 2011-05-31 | 148146.72092 |
f, ax = plt.subplots(figsize=(15, 8))
g = sns.lineplot(x=df_prev.index, y=df_prev['Sales'], data=df_prev, color='g', linewidth = 2)
g.set(xlabel="Tempo")
g.set(ylabel="Valor de vendas")
g.set_title('Total de vendas por mês', weight='bold')
plt.show()
# estatística descritiva
df_prev.describe()
| Sales | |
|---|---|
| count | 48.000000 |
| mean | 263385.456456 |
| std | 89092.605518 |
| min | 128833.470340 |
| 25% | 198972.238920 |
| 50% | 252263.834350 |
| 75% | 309568.278820 |
| max | 508954.731560 |
# separando o dataframe em dados de treino (75%) e teste (25%)
train_size = int(0.75*len(df_prev))
train, test = train_test_split(df_prev, train_size=train_size)
# Gráfico do ACF e frequencia dos dados
tsdisplay(treino, lag_max=20)
C:\Users\Andre\anaconda3\lib\site-packages\pmdarima\utils\visualization.py:219: FutureWarning: the 'unbiased'' keyword is deprecated, use 'adjusted' instead
# treinando o modelo auto_arima para obter os melhores parâmetros p, d, q
baseline_model = auto_arima(train, suppress_warnings=True, start_p=0, start_q=0,
max_p=5, max_q=5, stepwise=True, trace=True, seasonal=True, m=3)
Performing stepwise search to minimize aic ARIMA(0,0,0)(1,1,1)[3] intercept : AIC=1093.367, Time=0.08 sec ARIMA(0,0,0)(0,1,0)[3] intercept : AIC=1097.398, Time=0.01 sec ARIMA(1,0,0)(1,1,0)[3] intercept : AIC=1090.427, Time=0.04 sec ARIMA(0,0,1)(0,1,1)[3] intercept : AIC=1089.502, Time=0.04 sec ARIMA(0,0,0)(0,1,0)[3] : AIC=1099.650, Time=0.01 sec ARIMA(0,0,1)(0,1,0)[3] intercept : AIC=1093.229, Time=0.04 sec ARIMA(0,0,1)(1,1,1)[3] intercept : AIC=1090.850, Time=0.09 sec ARIMA(0,0,1)(0,1,2)[3] intercept : AIC=1091.291, Time=0.06 sec ARIMA(0,0,1)(1,1,0)[3] intercept : AIC=1091.983, Time=0.05 sec ARIMA(0,0,1)(1,1,2)[3] intercept : AIC=1092.846, Time=0.11 sec ARIMA(0,0,0)(0,1,1)[3] intercept : AIC=1091.370, Time=0.03 sec ARIMA(1,0,1)(0,1,1)[3] intercept : AIC=1090.604, Time=0.07 sec ARIMA(0,0,2)(0,1,1)[3] intercept : AIC=1093.339, Time=0.05 sec ARIMA(1,0,0)(0,1,1)[3] intercept : AIC=1087.891, Time=0.04 sec ARIMA(1,0,0)(0,1,0)[3] intercept : AIC=1093.428, Time=0.02 sec ARIMA(1,0,0)(1,1,1)[3] intercept : AIC=1089.848, Time=0.10 sec ARIMA(1,0,0)(0,1,2)[3] intercept : AIC=1089.775, Time=0.05 sec ARIMA(1,0,0)(1,1,2)[3] intercept : AIC=1091.770, Time=0.09 sec ARIMA(2,0,0)(0,1,1)[3] intercept : AIC=1089.888, Time=0.06 sec ARIMA(2,0,1)(0,1,1)[3] intercept : AIC=1090.514, Time=0.11 sec ARIMA(1,0,0)(0,1,1)[3] : AIC=1090.670, Time=0.03 sec Best model: ARIMA(1,0,0)(0,1,1)[3] intercept Total fit time: 1.250 seconds
# utilizando o modelo treinado para previsão com dados de teste
pred = baseline_model.predict(n_periods=test.shape[0])
fig = go.Figure()
x = np.arange(test.shape[0])
fig.add_trace(go.Scatter(x=test.index, y=test['Sales'], mode='lines', name='Actual', showlegend=True))
fig.add_trace(go.Scatter(x=test.index, y=pred, mode='lines', name='Predicated'))
fig.update_layout(title='Baseline model',
xaxis_title='Tempo',
yaxis_title='Sales')
fig.show()
# métrica de precisão smape recomendada pela documentação do módulo
# smape(Symmetric Mean Absolute Percentage Error - Erro Percentual Absoluto Médio Simétrico)
# Medida de precisão baseada em erros percentuais.
smape(test, pred)
25.30811464286546
O modelo erra cerca de 25% dos dados de teste
# métrica de precisão Root Mean Squared Error - Raiz Quadrática Média dos Erros
# Medida das diferenças entre os valores previstos e os valores observados
mse = mean_squared_error(test, pred)
rmse = sqrt(mse)
print(f'RMSE: {rmse}')
RMSE: 7332072.626556568
# plotando o histograma dos dados
df_prev.hist()
array([[<AxesSubplot:title={'center':'Sales'}>]], dtype=object)
O gráfico mostra uma distribuição enviesada à direita
# criando cópia do DataFrame
df_bc = df_prev
# Transformação utilizando box-cox para obtenção de distribuição normal
df_bc['Sales'], lam = boxcox(df_bc['Sales'])
print('Lambda:', lam)
Lambda: 0.04436407567577947
# visualizando as últimas 5 linhas do gráfico
df_bc.tail()
| Sales | |
|---|---|
| Order Date | |
| 2014-08-31 | 17.547219 |
| 2014-09-30 | 17.431516 |
| 2014-10-31 | 17.436857 |
| 2014-11-30 | 17.836803 |
| 2014-12-31 | 17.526665 |
# histograma do dataframe com transformação box-cox
df_bc.hist()
array([[<AxesSubplot:title={'center':'Sales'}>]], dtype=object)
O histograma com transformação box-cox mostra uma distriuição próxima ao normal
# função para verificar a estacionaridade da série com teste adfuller
def stat_test(series_value):
stat_test = adfuller(series_value)
test_result = pd.Series(stat_test[0:4], index = ['Test statistics', 'p-value', 'Lags Used', 'Number of used observations'])
print(test_result)
stat_test(df_bc['Sales'])
Test statistics -0.728200 p-value 0.839309 Lags Used 2.000000 Number of used observations 45.000000 dtype: float64
# transformando a série com a função logarítimica e verificando se obteve a estacionaridade da série
log_series = pd.Series(np.log(df_bc['Sales'].values), index = df_bc.index)
stat_test(log_series.values)
Test statistics -0.804955 p-value 0.817662 Lags Used 2.000000 Number of used observations 45.000000 dtype: float64
p-value muito maior que 0.05. Portanto a série não apresenta estacionaridade com transformação por função logarítimica
# Funções para diferenciar a série temporal
def difference(data, interval):
return [data[i] - data[i - interval] for i in range(interval, len(data))]
# Função para inverter a diferença
def invert_difference(orig_data, diff_data, interval):
return [diff_data[i-interval] + orig_data[i-interval] for i in range(interval, len(orig_data))]
# Transformação por diferenciação
diff_series = pd.DataFrame(difference(df_bc['Sales'], 1), index = df_bc.index[1:], columns = ['Sales'])
diff_series.dropna(inplace = True)
stat_test(diff_series)
Test statistics -9.073187e+00 p-value 4.247509e-15 Lags Used 1.000000e+01 Number of used observations 3.600000e+01 dtype: float64
O p-value do teste com essa diferenciação é praticamente zero. Portanto, a série se tornou estacionária.
# visualizando a série com a transformação por diferenciação
diff_series.plot()
<AxesSubplot:xlabel='Order Date'>
# visualizando a tendência, sazionalidade e resíduos da série
result = seasonal_decompose(diff_series)
# configurando o tamanho do gráfico
fig, (ax1,ax2,ax3, ax4) = plt.subplots(4,1, figsize=(12,8))
result.observed.plot(ax=ax1).set_title('Observado')
result.trend.plot(ax=ax2).set_title('Tendência')
result.seasonal.plot(ax=ax3).set_title('Sazionalidade')
result.resid.plot(ax=ax4).set_title('Resíduos')
plt.tight_layout()
Observa-se no gráfico que há sazionalidade na série. com tendência de queda nos meses de Janeiro e Julho
# plotando o gráfico de autocorrelação
plot_acf(diff_series, lags=40)
plt.show()
# plotando o grráfico de parcial auto-correlação
plot_pacf(diff_series, lags=20)
plt.show()
C:\Users\Andre\anaconda3\lib\site-packages\statsmodels\regression\linear_model.py:1434: RuntimeWarning: invalid value encountered in sqrt
# utilizando o modelo auto-arima para melhor identificação dos termos p,d, q da série transformada
stepwise_model = auto_arima(diff_series, start_p=1, start_q=1, max_p=6, max_q=6, m=3, start_P=0, seasonal=True, d=1, D=1, trace=True, error_action='ignore',
supress_warning=True, stepwise=False)
ARIMA(0,1,0)(0,1,0)[3] : AIC=86.869, Time=0.03 sec ARIMA(0,1,0)(0,1,1)[3] : AIC=67.395, Time=0.08 sec ARIMA(0,1,0)(0,1,2)[3] : AIC=69.321, Time=0.10 sec ARIMA(0,1,0)(1,1,0)[3] : AIC=82.730, Time=0.04 sec ARIMA(0,1,0)(1,1,1)[3] : AIC=69.377, Time=0.12 sec ARIMA(0,1,0)(1,1,2)[3] : AIC=inf, Time=0.38 sec ARIMA(0,1,0)(2,1,0)[3] : AIC=79.688, Time=0.04 sec ARIMA(0,1,0)(2,1,1)[3] : AIC=66.442, Time=0.09 sec ARIMA(0,1,0)(2,1,2)[3] : AIC=64.280, Time=0.25 sec ARIMA(0,1,1)(0,1,0)[3] : AIC=inf, Time=0.12 sec ARIMA(0,1,1)(0,1,1)[3] : AIC=inf, Time=0.19 sec ARIMA(0,1,1)(0,1,2)[3] : AIC=inf, Time=0.20 sec ARIMA(0,1,1)(1,1,0)[3] : AIC=inf, Time=0.14 sec ARIMA(0,1,1)(1,1,1)[3] : AIC=inf, Time=0.33 sec ARIMA(0,1,1)(1,1,2)[3] : AIC=inf, Time=0.31 sec ARIMA(0,1,1)(2,1,0)[3] : AIC=inf, Time=0.19 sec ARIMA(0,1,1)(2,1,1)[3] : AIC=inf, Time=0.31 sec ARIMA(0,1,1)(2,1,2)[3] : AIC=inf, Time=0.46 sec ARIMA(0,1,2)(0,1,0)[3] : AIC=inf, Time=0.12 sec ARIMA(0,1,2)(0,1,1)[3] : AIC=inf, Time=0.28 sec ARIMA(0,1,2)(0,1,2)[3] : AIC=inf, Time=0.29 sec ARIMA(0,1,2)(1,1,0)[3] : AIC=inf, Time=0.22 sec ARIMA(0,1,2)(1,1,1)[3] : AIC=inf, Time=0.32 sec ARIMA(0,1,2)(1,1,2)[3] : AIC=inf, Time=0.44 sec ARIMA(0,1,2)(2,1,0)[3] : AIC=inf, Time=0.20 sec ARIMA(0,1,2)(2,1,1)[3] : AIC=inf, Time=0.45 sec ARIMA(1,1,0)(0,1,0)[3] : AIC=71.892, Time=0.02 sec ARIMA(1,1,0)(0,1,1)[3] : AIC=53.717, Time=0.07 sec ARIMA(1,1,0)(0,1,2)[3] : AIC=55.715, Time=0.12 sec ARIMA(1,1,0)(1,1,0)[3] : AIC=67.033, Time=0.07 sec ARIMA(1,1,0)(1,1,1)[3] : AIC=55.716, Time=0.09 sec ARIMA(1,1,0)(1,1,2)[3] : AIC=inf, Time=0.37 sec ARIMA(1,1,0)(2,1,0)[3] : AIC=65.790, Time=0.05 sec ARIMA(1,1,0)(2,1,1)[3] : AIC=54.734, Time=0.11 sec ARIMA(1,1,0)(2,1,2)[3] : AIC=53.355, Time=0.30 sec ARIMA(1,1,1)(0,1,0)[3] : AIC=inf, Time=0.18 sec ARIMA(1,1,1)(0,1,1)[3] : AIC=inf, Time=0.39 sec ARIMA(1,1,1)(0,1,2)[3] : AIC=inf, Time=0.40 sec ARIMA(1,1,1)(1,1,0)[3] : AIC=inf, Time=0.50 sec ARIMA(1,1,1)(1,1,1)[3] : AIC=inf, Time=0.40 sec ARIMA(1,1,1)(1,1,2)[3] : AIC=inf, Time=0.30 sec ARIMA(1,1,1)(2,1,0)[3] : AIC=inf, Time=0.26 sec ARIMA(1,1,1)(2,1,1)[3] : AIC=inf, Time=0.47 sec ARIMA(1,1,2)(0,1,0)[3] : AIC=inf, Time=0.35 sec ARIMA(1,1,2)(0,1,1)[3] : AIC=inf, Time=0.54 sec ARIMA(1,1,2)(0,1,2)[3] : AIC=inf, Time=0.68 sec ARIMA(1,1,2)(1,1,0)[3] : AIC=inf, Time=0.45 sec ARIMA(1,1,2)(1,1,1)[3] : AIC=inf, Time=0.59 sec ARIMA(1,1,2)(2,1,0)[3] : AIC=45.555, Time=0.51 sec ARIMA(2,1,0)(0,1,0)[3] : AIC=73.351, Time=0.33 sec ARIMA(2,1,0)(0,1,1)[3] : AIC=inf, Time=0.23 sec ARIMA(2,1,0)(0,1,2)[3] : AIC=inf, Time=0.46 sec ARIMA(2,1,0)(1,1,0)[3] : AIC=63.305, Time=0.07 sec ARIMA(2,1,0)(1,1,1)[3] : AIC=inf, Time=0.51 sec ARIMA(2,1,0)(1,1,2)[3] : AIC=inf, Time=0.91 sec ARIMA(2,1,0)(2,1,0)[3] : AIC=62.998, Time=0.07 sec ARIMA(2,1,0)(2,1,1)[3] : AIC=inf, Time=0.38 sec ARIMA(2,1,1)(0,1,0)[3] : AIC=inf, Time=0.27 sec ARIMA(2,1,1)(0,1,1)[3] : AIC=inf, Time=0.53 sec ARIMA(2,1,1)(0,1,2)[3] : AIC=inf, Time=0.44 sec ARIMA(2,1,1)(1,1,0)[3] : AIC=inf, Time=0.28 sec ARIMA(2,1,1)(1,1,1)[3] : AIC=inf, Time=0.34 sec ARIMA(2,1,1)(2,1,0)[3] : AIC=inf, Time=1.02 sec ARIMA(2,1,2)(0,1,0)[3] : AIC=inf, Time=0.73 sec ARIMA(2,1,2)(0,1,1)[3] : AIC=inf, Time=0.88 sec ARIMA(2,1,2)(1,1,0)[3] : AIC=inf, Time=0.37 sec Best model: ARIMA(1,1,2)(2,1,0)[3] Total fit time: 20.819 seconds
O modelo auto-arima encontrou o modelo SARIMA com termos p, d, q igual à (1,1,2), respectivamente. E ordem sazional de (2,1,0).
# exibindo o valor do critério de informação de Akaike do modelo selecionado
stepwise_model.aic()
45.554764173561715
O critério de informação de Akaike (AIC) é um estimador de erro de predição e, portanto, da qualidade relativa dos modelos estatísticos para um determinado conjunto de dados. Dada uma coleção de modelos para os dados, o AIC estima a qualidade de cada modelo, em relação a cada um dos outros modelos. Portanto, o AIC fornece um meio para a seleção do modelo.
train_size = int(0.75*len(df_prev))
treino, teste = train_test_split(diff_series, train_size=train_size)
# ajuste do modelo e predição com validação cruzada nos dados de treino
model = pm.ARIMA(order=(1,1,2),
seasonal_order=(2,1,0,3),
suppress_warnings=True)
cv = model_selection.SlidingWindowForecastCV(window_size=3, step=1, h=3)
predictions = model_selection.cross_val_predict(model, treino['Sales'], cv=cv, verbose=1, averaging="mean")
x_axis = np.arange(treino.shape[0])
n_test = predictions.shape[0]
fig = go.Figure()
fig.add_trace(go.Scatter(x=treino.index, y=treino['Sales'], name='Valor real'))
fig.add_trace(go.Scatter(x=treino.index, y=predictions, name='Predito'))
fig.update_layout(title='Previsão nos dados de treino', xaxis_title='Tempo', yaxis_title='Sales')
fig.show()
# Predição nos dados de teste
model = pm.ARIMA(order=(1,1,2),
seasonal_order=(2,1,0,3),
suppress_warnings=True)
model.fit(treino['Sales'])
predictions = model.predict(n_periods=teste.shape[0])
x = np.arange(teste.shape[0])
fig = go.Figure()
fig.add_trace(go.Scatter(x=teste.index, y=teste['Sales'], mode='lines', name='Valor real', showlegend=True))
fig.add_trace(go.Scatter(x=teste.index, y=predictions, mode='lines', name='Predito'))
fig.update_layout(title='Previsão nos dados de teste',
xaxis_title='Days',
yaxis_title='New cases')
fig.show()
# métrica de precisão Root Mean Squared Error - Raiz Quadrática Média dos Erros
# Medida das diferenças entre os valores previstos e os valores observados
mse = mean_squared_error(teste, predictions)
rmse = sqrt(mse)
print(f'RMSE: {rmse}')
RMSE: 0.4191068183855903
# utilizando a função definida para retirar a trasformação por diferenciação
diff_inv = invert_difference(df_bc['Sales'], diff_series['Sales'], 1)
diff_inv = pd.DataFrame(diff_inv, index=df_bc.index[1:], columns=['Sales'])
# utilizando a função definida para retirar a trasformação box-cox
ts = inv_boxcox(diff_inv['Sales'], lam)
ts = pd.DataFrame(ts, columns=['Sales'])
ts.plot()
<AxesSubplot:xlabel='Order Date'>
# utilizando o modelo sarima para predição de novos dados
model = pm.ARIMA(order=(1,1,2),
seasonal_order=(2,1,0,3),
suppress_warnings=True)
# Fit on the difference score
model.fit(ts['Sales'])
pred_arima = model.predict(n_periods=12)
# definindo o índice de 12 meses à partir de Janeiro de 2015
predict_index = pd.date_range(start='1/1/2015', periods=12, freq='M')
# criando DataFrame com os dados preditos
pred_arima = pd.DataFrame(pred_arima, index=predict_index, columns=['Sales'])
# Visualização da série temporal com predição
fig = px.line(ts.append(pred_arima), y="Sales", title='Previsão de vendas para 2015')
fig.show()
sns.set_theme(style="darkgrid", font_scale=1.8)
f, ax = plt.subplots(figsize=(16, 8))
g = sns.lineplot(x=ts.index, y=ts['Sales'], data=ts, linewidth=3)
g = sns.lineplot(x=pred_arima.index, y=pred_arima['Sales'], data=pred_arima, linewidth=3)
g.set(xlabel="Ano", ylabel="Soma das vendas")
g.set_title("Previsão de vendas para 2015", weight='bold')
Text(0.5, 1.0, 'Previsão de vendas para 2015')
sns.set_style('whitegrid')
sns.light_palette("seagreen", as_cmap=True)
sns.set(font_scale = 1.5);
f, ax = plt.subplots(figsize=(16, 8))
g = sns.barplot(x=pred_arima.index.month_name(), y=pred_arima['Sales'], palette='crest')
g.set(ylabel="Vendas")
g.set_title("Previsão de vendas por mês em 2015", weight='bold')
Text(0.5, 1.0, 'Previsão de vendas por mês em 2015')
# soma total de vendas por ano com previsão
total_2015 = pd.DataFrame(pred_arima.groupby(pred_arima.index.year).Sales.sum())
prev_total = pd.concat([vendas_ano, total_2015], axis=0)
fig = px.funnel(prev_total, x=prev_total.index, y=prev_total['Sales'])
fig.show()
# taxa de crescimento com os dados preditos
pd.DataFrame(prev_total.Sales.pct_change().mul(100).round(2)).plot()
<AxesSubplot:>
O conteúdo da base dados entre o período de 2011 à 2014, mostra que o segmento dos clientes se divide em: consumidor, corporativo e home office. Sendo os clientes do segmento consumidor, os principais, com valores utrapassando os 6 milhões.
Além disso, clientes dos EUA são os que mais compram, chegando em quase 2.5 milhões. Em termos de região, países do APAC (Ásia-Pacífico), formado por Ásia Oriental, Sul da Ásia, Sudeste da Ásia e Oceania, possuem maiores valores de venda, responsável por 26.7% do total de vendas. Sendo os produtos da categoria tecnologia os mais vendidos, ultrapassando 4 milhões do total de venda do período.
Outro fator identificado tem relação a descontos e taxa de entrega. Os dados mostram que a média mensal de custo de entrega aumentam a medida em que o valor de venda sobe. Sendo na região da Ásia, os lugares com maiores custos. Ainda, algumas vendas tiveram mais 80% de desconto. Sendo o produto mesa, o qual teve quase 30% de desconto, refletindo no lucro final do período, trazendo prejuízo a empresa referente a este produto. Em contrapartida, o produto copiadora trouxe os melhores resultados, com mais de 100 mil em valor de venda.
A partir desses dados, foi possível prever as vendas para o ano de 2015. Esperando-se assim, um total de venda de aproximadamente 6.85 Milhões, com taxa de crescimento de 59% em relação a 2014.